#pragma once

#include <string_view>
#include <cstdio>

#include "types.h"
#include "memory.h"

class hex_loader {
public:
    memory_block load(const std::string_view filename, size_t mem_size) {
        using type_func_t = FILE * (hex_loader::*)(FILE*, byte*, int, int);

        constexpr const type_func_t callback_map[] = {
            &hex_loader::data_record,
            &hex_loader::end_of_file_record,
            &hex_loader::extended_segment_address_record,
            &hex_loader::start_segment_address_record,
            &hex_loader::extended_linear_address_record,
            &hex_loader::start_linear_address_record,
        };

        const size_t offset = sizeof(size_t) / sizeof(byte);

        auto* result_mem = (byte*)calloc(mem_size + offset, sizeof(byte));
        *reinterpret_cast<size_t*>(result_mem) = mem_size;

        result_mem += offset;

        FILE* fp;
        fopen_s(&fp, filename.data(), "r");

        while (fp && !feof(fp)) {
            int len, addr, type;
            fscanf_s(fp, ":%02X%04X%02X", &len, &addr, &type, 9);

        #ifdef __DEBUG__
            printf("%d: 0x%X, 0x%X, 0x%X\n", ftell(fp), len, addr, type);
        #endif

            fp = next_line((this->*callback_map[type])(fp, result_mem, len, addr));
        }

        return { result_mem };
    }

private:
    FILE* next_line(FILE* fp) {
        if (!fp) return fp;

        int end;
        fscanf_s(fp, "%02X\x0d\x0a", &end, 4);
        /*

        */
        return fp;
    }

    FILE* data_record(FILE* fp, byte* mem, int len, int addr) {
        int data = 0;

        addr = get_address(addr);

        for (int i = 0; i < len; ++i) {
            fscanf_s(fp, "%02X", &data, 2);
            mem[addr + i] = byte(data);

        #ifdef __DEBUG__
            printf("%d: [%08XH] - 0x%X\n", ftell(fp), addr + i, data);
        #endif
        }

        return fp;
    }

    FILE* end_of_file_record(FILE* fp, byte* mem, int len, int addr) {
        fclose(fp);
        return nullptr;
    }

    FILE* extended_segment_address_record(FILE* fp, byte* mem, int len, int addr) {
        int64_t data;
        fscanf_s(fp, "%04X", &data, 4);
        _segment_base = data << 8;

    #ifdef __DEBUG__
        printf("%d: seg_base[%08XH]\n", ftell(fp), _segment_base);
    #endif

        return fp;
    }

    FILE* start_segment_address_record(FILE* fp, byte* mem, int len, int addr) {
        return data_record(fp, mem, len, addr + _segment_base);
    }

    FILE* extended_linear_address_record(FILE* fp, byte* mem, int len, int addr) {
        int64_t data;
        fscanf_s(fp, "%04X", &data, 4);
        _linear_base = data << 16;

    #ifdef __DEBUG__
        printf("%d: lin_base[%08XH]\n", ftell(fp), _linear_base);
    #endif
        return fp;
    }

    FILE* start_linear_address_record(FILE* fp, byte* mem, int len, int addr) {
        return data_record(fp, mem, len, addr + _linear_base);
    }

    constexpr size_t get_address(size_t addr) const {
        return _linear_base + _segment_base + addr;
    }

    size_t _segment_base = 0, _linear_base = 0;
};